﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Kodefabrikken.DDD.Entity;
using Kodefabrikken.DDD.Entity.Experimental;
using Kodefabrikken.DDD.Specification;

namespace Kodefabrikken.DDD.Repository.Experimental
{
    public interface IRepository2<TEntity> where TEntity : IEntity
    {
        IEnumerable<TEntity> FindBySpecification(Specification<TEntity> specification);
    }

    public interface IKeyedRepository2<TKeyedEntity, TKey> : IRepository2<TKeyedEntity> where TKeyedEntity : IKeyedEntity<TKey>
    {
    }

    public static class IKeyedRepository2Extensions
    {
        public static TKeyedEntity FindByKey<TKeyedEntity, TKey>(this IKeyedRepository2<TKeyedEntity, TKey> obj, IIdentity<TKey> key)
            where TKeyedEntity : IKeyedEntity<TKey>
        {
            var KeyFinder = GetSpecification<TKeyedEntity, TKey>();

            var spec = KeyFinder.Bind(key);

            return obj.FindBySpecification(spec).SingleOrDefault();
        }

        private static Dictionary<string, object> gSpecifications = new Dictionary<string, object>();
        private static ParameterizedSpecification<TKeyedEntity, IIdentity<TKey>> GetSpecification<TKeyedEntity, TKey>()
            where TKeyedEntity : IKeyedEntity<TKey>
        {
            ParameterizedSpecification<TKeyedEntity, IIdentity<TKey>> specification;
            object obj;
            if (gSpecifications.TryGetValue(typeof(TKeyedEntity).AssemblyQualifiedName, out obj))
            {
                specification = (ParameterizedSpecification<TKeyedEntity, IIdentity<TKey>>)obj;
            }
            else
            {
                var param1 = Expression.Parameter(typeof(TKeyedEntity), "entity");
                var param2 = Expression.Parameter(typeof(IIdentity<TKey>), "key");
                var member = Expression.MakeMemberAccess(param1, typeof(TKeyedEntity).GetMembers().Single(p => p.Name == "Key"));
                var exp = Expression.Call(member, typeof(IEquatable<IIdentity<TKey>>).GetMethod("Equals", new Type[]{typeof(IIdentity<TKey>)}), new Expression[] {param2});

                var lambda = Expression<Func<TKeyedEntity, IIdentity<TKey>, bool>>.Lambda<Func<TKeyedEntity, IIdentity<TKey>, bool>>(exp, param1, param2);

                specification = new ParameterizedSpecification<TKeyedEntity, IIdentity<TKey>>(lambda);

                gSpecifications.Add(typeof(TKeyedEntity).AssemblyQualifiedName, specification);
            }

            return specification;
        }
    }


    public interface IRepository<TEntity, TKey>
       where TEntity : IIdentity<TKey>
    {
        IEnumerable<TEntity> FindBySpecification(Specification<TEntity> specification);
    }

    public interface IRepositoryCreate<TEntity>
    {
        TEntity Create();
    }

    public interface IRepositoryDelete<TEntity>
    {
        void Delete(TEntity entity);
    }

    public static class IRepositoryExtensions
    {
        public static TEntity FindByKey<TEntity, TKey>(this IRepository<TEntity, TKey> obj, TKey key)
            where TEntity : IIdentity<TKey>
        {
            var KeyFinder = GetSpecification<TEntity, TKey>();

            var spec = KeyFinder.Bind(key);

            return obj.FindBySpecification(spec).SingleOrDefault();
        }

        private static Dictionary<string, object> gSpecifications = new Dictionary<string, object>();
        private static ParameterizedSpecification<TEntity, TKey> GetSpecification<TEntity, TKey>()
            where TEntity : IIdentity<TKey>
        {
            ParameterizedSpecification<TEntity, TKey> specification;
            object obj;
            if (gSpecifications.TryGetValue(typeof(TEntity).AssemblyQualifiedName, out obj))
            {
                specification = (ParameterizedSpecification<TEntity, TKey>)obj;
            }
            else
            {
                var param1 = Expression.Parameter(typeof(TEntity), "entity");
                var param2 = Expression.Parameter(typeof(TKey), "key");
                var member = Expression.MakeMemberAccess(param1, typeof(TEntity).GetMembers().Single(p => p.Name == "Value"));
                var exp = Expression.Equal(member, param2);
                var lambda = Expression<Func<TEntity, TKey, bool>>.Lambda<Func<TEntity, TKey, bool>>(exp, param1, param2);

                specification = new ParameterizedSpecification<TEntity, TKey>(lambda);

                gSpecifications.Add(typeof(TEntity).AssemblyQualifiedName, specification);
            }

            return specification;
        }
    }
}
